﻿using Sidvall.Collections;
using Sidvall.Net.Mime;
using Sidvall.Serialization;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;

namespace Sidvall.Net
{
    public sealed class HttpClient : IHttpClient
    {
        #region Public Members

        #region AllowAutoRedirect

        public bool AllowAutoRedirect
        {
            get
            {
                return this.Settings.AllowAutoRedirect;
            }
            set
            {
                if (this.Settings.AllowAutoRedirect == value)
                    return;
                this.Settings.AllowAutoRedirect = value;
                InitializeHttpClientHandler();
            }
        }

        #endregion
        #region AuthenticationHeader

        public AuthenticationHeader AuthenticationHeader
        {
            get
            {
                return this.Settings.AuthenticationHeader;
            }
            set
            {
                if (AuthenticationHeader.IsEqual(this.Settings.AuthenticationHeader, value))
                    return;
                this.Settings.AuthenticationHeader = value;
                InitializeAuthenticationHeader(_ClientProxy, this.Settings.AuthenticationHeader);
            }
        }

        #endregion
        #region BaseAddress

        public string BaseAddress
        {
            get
            {
                return this.Settings.BaseAddress;
            }
            set
            {
                if (this.Settings.BaseAddress.Equals(value, true, System.StringComparison.OrdinalIgnoreCase))
                    return;
                this.Settings.BaseAddress = value;
                InitializeBaseAddress(_ClientProxy, this.Settings.BaseAddress);
            }
        }

        #endregion
        #region CustomHeaders

        public NameValueHeaderValueCollection CustomHeaders
        {
            get
            {
                return this.Settings.CustomHeaders;
            }
        }

        #endregion
        #region ProxySettings

        public ProxySettings ProxySettings
        {
            get
            {
                return this.Settings.ProxySettings;
            }
            set
            {
                if (ProxySettings.IsEqual(this.Settings.ProxySettings, value))
                    return;
                this.Settings.ProxySettings = value;
                InitializeHttpClientHandler();
            }
        }

        #endregion
        #region PreAuthenticate

        public bool? PreAuthenticate
        {
            get
            {
                return this.Settings.PreAuthenticate;
            }
            set
            {
                if (this.Settings.PreAuthenticate == value)
                    return;
                this.Settings.PreAuthenticate = value;
                InitializeHttpClientHandler();
            }
        }

        #endregion
        #region Range

        public RangeHeaderValue Range
        {
            get
            {
                return this.Settings.Range;
            }
            set
            {
                if (RangeHeaderValue.IsEqual(this.Settings.Range, value))
                    return;
                this.Settings.Range = value;
                InitializeRange(_ClientProxy, this.Settings.Range);
            }
        }

        #endregion
        #region ResponseTypes

        public MediaTypeHeaderCollection ResponseTypes
        {
            get
            {
                return this.Settings.ResponseTypes;
            }
        }

        #endregion
        #region Timeout

        public System.TimeSpan Timeout
        {
            get
            {
                return this.ClientProxy.Timeout;
            }
            set
            {
                if (this.ClientProxy.Timeout == value)
                    return;
                this.Settings.Timeout = value;
                InitializeTimeout(_ClientProxy, this.Settings.Timeout);
            }
        }

        #endregion
        #region UseDefaultCredentials

        public bool? UseDefaultCredentials
        {
            get
            {
                return this.Settings.UseDefaultCredentials;
            }
            set
            {
                if (this.Settings.UseDefaultCredentials == value)
                    return;
                this.Settings.UseDefaultCredentials = value;
                InitializeHttpClientHandler();
            }
        }

        #endregion

        #region Dispose

        public void Dispose()
        {
            if (this._Handler != null)
            {
                this._Handler.Dispose();
                this._Handler = null;
            }
            if (this._ClientProxy != null)
            {
                this._ClientProxy.Dispose();
                this._ClientProxy = null;
            }
            System.GC.SuppressFinalize(this);
        }

        #endregion

        #region DeleteAsync

        public async Task<HttpResponse> DeleteAsync(System.Uri requestUri, CancellationToken cancellationToken)
        {
            if (requestUri == null)
                throw new System.ArgumentNullException("url");
            var responseMessage = await this.ClientProxy.DeleteAsync(requestUri, cancellationToken).ConfigureAwait(false);
            return HttpManager.CreateResponseMessage(responseMessage);
        }

        #endregion
        #region GetAsync

        public async Task<HttpResponse> GetAsync(System.Uri requestUri, HttpCompletionOption completionOption, CancellationToken cancellationToken)
        {
            if (requestUri == null)
                throw new System.ArgumentNullException("url");
            var responseMessage = await this.ClientProxy.GetAsync(requestUri, GetCompletionOption(completionOption), cancellationToken).ConfigureAwait(false);
            return HttpManager.CreateResponseMessage(responseMessage);
        }

        #endregion
        #region PostAsync

        public async Task<HttpResponse> PostAsync(System.Uri requestUri, string json, CancellationToken cancellationToken)
        {
            return await PostAsync(requestUri, GetContent(json, MediaType.ApplicationJson), cancellationToken).ConfigureAwait(false);
        }
        public async Task<HttpResponse> PostAsync(System.Uri requestUri, string content, MediaType mediaType, CancellationToken cancellationToken)
        {
            return await PostAsync(requestUri, GetContent(content, mediaType), cancellationToken).ConfigureAwait(false);
        }
        public async Task<HttpResponse> PostAsync(System.Uri requestUri, Dictionary<string, string> form, CancellationToken cancellationToken)
        {
            return await PostAsync(requestUri, GetContent(form), cancellationToken).ConfigureAwait(false);
        }
        public async Task<HttpResponse> PostAsync(System.Uri requestUri, System.IO.Stream content, int bufferSize, CancellationToken cancellationToken)
        {
            return await PostAsync(requestUri, GetContent(content, bufferSize), cancellationToken).ConfigureAwait(false);
        }
        private async Task<HttpResponse> PostAsync(System.Uri requestUri, System.Net.Http.HttpContent content, CancellationToken cancellationToken)
        {
            var responseMessage = await this.ClientProxy.PostAsync(requestUri, content, cancellationToken).ConfigureAwait(false);
            return HttpManager.CreateResponseMessage(responseMessage);
        }

        #endregion
        #region PutAsync

        public async Task<HttpResponse> PutAsync(System.Uri requestUri, string json, CancellationToken cancellationToken)
        {
            return await PutAsync(requestUri, GetContent(json, MediaType.ApplicationJson), cancellationToken).ConfigureAwait(false);
        }
        public async Task<HttpResponse> PutAsync(System.Uri requestUri, string content, MediaType mediaType, CancellationToken cancellationToken)
        {
            return await PutAsync(requestUri, GetContent(content, mediaType), cancellationToken).ConfigureAwait(false);
        }
        public async Task<HttpResponse> PutAsync(System.Uri requestUri, Dictionary<string, string> form, CancellationToken cancellationToken)
        {
            return await PutAsync(requestUri, GetContent(form), cancellationToken).ConfigureAwait(false);
        }
        public async Task<HttpResponse> PutAsync(System.Uri requestUri, System.IO.Stream content, int bufferSize, CancellationToken cancellationToken)
        {
            return await PutAsync(requestUri, GetContent(content, bufferSize), cancellationToken).ConfigureAwait(false);
        }
        private async Task<HttpResponse> PutAsync(System.Uri requestUri, System.Net.Http.HttpContent content, CancellationToken cancellationToken)
        {
            var responseMessage = await this.ClientProxy.PutAsync(requestUri, content, cancellationToken).ConfigureAwait(false);
            return HttpManager.CreateResponseMessage(responseMessage);
        }

        #endregion
        #region SendAsync

        public async Task<HttpResponse> SendAsync(System.Uri requestUri, HttpMethod method, HttpCompletionOption completionOption, CancellationToken cancellationToken)
        {
            return await SendAsync(requestUri, method, (System.Net.Http.HttpContent)null, completionOption, cancellationToken).ConfigureAwait(false);
        }
        public async Task<HttpResponse> SendAsync(System.Uri requestUri, HttpMethod method, string json, HttpCompletionOption completionOption, CancellationToken cancellationToken)
        {
            return await SendAsync(requestUri, method, GetContent(json, MediaType.ApplicationJson), completionOption, cancellationToken).ConfigureAwait(false);
        }
        public async Task<HttpResponse> SendAsync(System.Uri requestUri, HttpMethod method, string content, MediaType mediaType, HttpCompletionOption completionOption, CancellationToken cancellationToken)
        {
            return await SendAsync(requestUri, method, GetContent(content, mediaType), completionOption, cancellationToken).ConfigureAwait(false);
        }
        public async Task<HttpResponse> SendAsync(System.Uri requestUri, HttpMethod method, Dictionary<string, string> form, HttpCompletionOption completionOption, CancellationToken cancellationToken)
        {
            return await SendAsync(requestUri, method, GetContent(form), completionOption, cancellationToken).ConfigureAwait(false);
        }
        public async Task<HttpResponse> SendAsync(System.Uri requestUri, HttpMethod method, System.IO.Stream content, int bufferSize, HttpCompletionOption completionOption, CancellationToken cancellationToken)
        {
            return await SendAsync(requestUri, method, GetContent(content, bufferSize), completionOption, cancellationToken).ConfigureAwait(false);
        }
        private async Task<HttpResponse> SendAsync(System.Uri requestUri, HttpMethod method, System.Net.Http.HttpContent content, HttpCompletionOption completionOption, CancellationToken cancellationToken)
        {
            var request = new System.Net.Http.HttpRequestMessage(GetMethod(method), requestUri);
            if (content != null)
                request.Content = content;
            var responseMessage = await this.ClientProxy.SendAsync(request, GetCompletionOption(completionOption), cancellationToken).ConfigureAwait(false);
            return HttpManager.CreateResponseMessage(responseMessage);
        }

        #endregion

        #region ToString

        public override string ToString()
        {
            return "HttpClient - System.Net.Http.HttpClient (PCL)";
        }

        #endregion

        #endregion
        #region Private Members

        #region ClientProxy

        private readonly object _Mutex = new object();
        private HttpClientHandler _Handler;
        private System.Net.Http.HttpClient _ClientProxy;
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
        private System.Net.Http.HttpClient ClientProxy
        {
            get
            {
                if (_ClientProxy == null)
                {
                    lock (_Mutex)
                    {
                        if (_ClientProxy == null)
                        {
                            _Handler = new HttpClientHandler();
                            InitializeHttpClientHandler();
                            var item = new System.Net.Http.HttpClient(_Handler, false);
                            InitializeBaseAddress(item, this.Settings.BaseAddress);
                            InitializeResponseTypes(item, this.Settings.ResponseTypes);
                            InitializeTimeout(item, this.Settings.Timeout);
                            InitializeRange(item, this.Settings.Range);
                            InitializeAuthenticationHeader(item, this.Settings.AuthenticationHeader);
                            InitializeLanguage(item, this.Settings.LanguagePolicy, this.Settings.LanguageHeaders);
                            InitializeCustomHeaders(item, this.Settings.CustomHeaders);
                            _ClientProxy = item;
                        }
                    }
                }
                return _ClientProxy;
            }
        }

        #endregion
        #region Settings

        private HttpClientSettings _Settings;
        private HttpClientSettings Settings
        {
            get
            {
                if (_Settings == null)
                    _Settings = new HttpClientSettings();
                return _Settings;
            }
            set
            {
                _Settings = value;
            }
        }

        #endregion

        #region GetContent

        private static System.Net.Http.HttpContent GetContent(Dictionary<string, string> form)
        {
            return new FormUrlEncodedContent(form);
        }
        private static System.Net.Http.HttpContent GetContent(string content, MediaType mediaType)
        {
            return new StringContent(content, System.Text.Encoding.UTF8, MediaTypeManager.GetMediaTypeName(mediaType));
        }
        private static System.Net.Http.HttpContent GetContent(System.IO.Stream content, int bufferSize)
        {
            return new StreamContent(content, bufferSize);
        }

        #endregion
        #region GetCompletionOption

        private static System.Net.Http.HttpCompletionOption GetCompletionOption(HttpCompletionOption completionOption)
        {
            switch (completionOption)
            {
                case HttpCompletionOption.ResponseHeadersRead:
                    return System.Net.Http.HttpCompletionOption.ResponseHeadersRead;
                default:
                    return System.Net.Http.HttpCompletionOption.ResponseContentRead;
            }
        }

        #endregion
        #region GetCredentials

        private static System.Net.NetworkCredential GetCredentials(CredentialsSettings credentials)
        {
            if (credentials == null)
                return null;
            if (string.IsNullOrWhiteSpace(credentials.Domain))
                return new System.Net.NetworkCredential(credentials.UserName, credentials.Password);
            return new System.Net.NetworkCredential(credentials.UserName, credentials.Password, credentials.Domain);
        }

        #endregion
        #region GetMethod

        private static System.Net.Http.HttpMethod GetMethod(HttpMethod method)
        {
            switch (method)
            {
                case HttpMethod.Delete:
                    return System.Net.Http.HttpMethod.Delete;
                case HttpMethod.Get:
                    return System.Net.Http.HttpMethod.Get;
                case HttpMethod.Head:
                    return System.Net.Http.HttpMethod.Head;
                case HttpMethod.Options:
                    return System.Net.Http.HttpMethod.Options;
                case HttpMethod.Put:
                    return System.Net.Http.HttpMethod.Put;
                case HttpMethod.Trace:
                    return System.Net.Http.HttpMethod.Trace;
                default:
                    return System.Net.Http.HttpMethod.Post;
            }
        }

        #endregion
        #region InitializeHttpClientHandler

        private void InitializeHttpClientHandler()
        {
            if (_Handler == null)
                return;
            _Handler.AllowAutoRedirect = this.Settings.AllowAutoRedirect;
            if (this.Settings.ProxySettings != null)
                InitializeProxy(_Handler, this.Settings.ProxySettings);
            else
                InitializeProxy(_Handler, Sidvall.Net.HttpSystemContext.Current.Proxy);
            if (this.Settings.PreAuthenticate != null)
                _Handler.PreAuthenticate = this.Settings.PreAuthenticate.Value;
            else if (Sidvall.Net.HttpSystemContext.Current.PreAuthenticate != null)
                _Handler.PreAuthenticate = Sidvall.Net.HttpSystemContext.Current.PreAuthenticate.Value;
            if (this.Settings.UseDefaultCredentials != null)
                _Handler.UseDefaultCredentials = this.Settings.UseDefaultCredentials.Value;
            else if (Sidvall.Net.HttpSystemContext.Current.UseDefaultCredentials != null)
                _Handler.UseDefaultCredentials = Sidvall.Net.HttpSystemContext.Current.UseDefaultCredentials.Value;
            if (this.Settings.MaxRequestContentBufferSize != null)
                _Handler.MaxRequestContentBufferSize = this.Settings.MaxRequestContentBufferSize.Value;
            if (_Handler.SupportsAutomaticDecompression)
                _Handler.AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate;
        }

        #endregion
        #region InitializeRange

        private static void InitializeRange(System.Net.Http.HttpClient item, RangeHeaderValue range)
        {
            if (item == null)
                return;
            if (range != null)
                item.DefaultRequestHeaders.Range = new System.Net.Http.Headers.RangeHeaderValue(range.From, range.To);
            else
                item.DefaultRequestHeaders.Range = null;
        }

        #endregion
        #region InitializeAuthenticationHeader

        private static void InitializeAuthenticationHeader(System.Net.Http.HttpClient item, AuthenticationHeader authenticationHeader)
        {
            if (item == null)
                return;
            if (authenticationHeader != null)
                item.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authenticationHeader.Scheme, authenticationHeader.Parameter);
            else
                item.DefaultRequestHeaders.Authorization = null;
        }

        #endregion
        #region InitializeBaseAddress

        private static void InitializeBaseAddress(System.Net.Http.HttpClient item, string baseAddress)
        {
            if (item == null)
                return;
            if (!string.IsNullOrWhiteSpace(baseAddress))
                item.BaseAddress = new System.Uri(baseAddress);
            else
                item.BaseAddress = null;
        }

        #endregion
        #region InitializeProxy

        private static void InitializeProxy(HttpClientHandler handler, ProxySettings settings)
        {
            if (handler == null)
                return;
            if (settings != null)
            {
                handler.Proxy = new Proxy(new System.Uri(settings.Address, System.UriKind.RelativeOrAbsolute))
                {
                    Credentials = GetCredentials(settings.Credentials),
                };
            }
            else
            {
                handler.Proxy = null;
            }
        }

        #endregion
        #region InitializeResponseTypes

        private static void InitializeResponseTypes(System.Net.Http.HttpClient item, MediaTypeHeaderCollection responseTypes)
        {
            if (item == null)
                return;
            item.DefaultRequestHeaders.Accept.Clear();
            if (responseTypes != null)
            {
                foreach (var responseType in responseTypes)
                {
                    var mediaType = MediaTypeManager.GetMediaTypeName(responseType.MediaType);
                    if (!string.IsNullOrWhiteSpace(mediaType))
                    {
                        if (responseType.Quality != null)
                            item.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType, responseType.Quality.Value));
                        else
                            item.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
                    }
                }
            }
        }

        #endregion
        #region InitializeTimeout

        private static void InitializeTimeout(System.Net.Http.HttpClient item, System.TimeSpan? timeout)
        {
            if (item == null)
                return;
            if (timeout != null)
                item.Timeout = timeout.Value;
        }

        #endregion
        #region InitializeLanguage

        private static void InitializeLanguage(System.Net.Http.HttpClient item, LanguagePolicy languagePolicy, LanguageHeaderCollection languageHeaders)
        {
            if (item == null)
                return;
            item.DefaultRequestHeaders.AcceptLanguage.Clear();
            switch (languagePolicy)
            {
                case LanguagePolicy.CurrentCulture:
                    Sidvall.SystemContext.Current.CultureManager.EnsureCorrectCurrentUICulture();
                    item.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue(System.Globalization.CultureInfo.CurrentUICulture.Name));
                    break;
                case LanguagePolicy.LanguageHeadersOrCurrentCulture:
                    if (!languageHeaders.IsNullOrEmpty())
                    {
                        foreach (var languageHeader in languageHeaders)
                        {
                            if (languageHeader.Quality != null)
                                item.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue(languageHeader.CultureName, languageHeader.Quality.Value));
                            else
                                item.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue(languageHeader.CultureName));
                        }
                    }
                    else
                    {
                        Sidvall.SystemContext.Current.CultureManager.EnsureCorrectCurrentUICulture();
                        item.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue(System.Globalization.CultureInfo.CurrentUICulture.Name));
                    }
                    break;
                case LanguagePolicy.None:
                    break;
                default:
                    throw new System.ArgumentException(nameof(languagePolicy));
            }
        }

        #endregion

        #region InitializeCustomHeaders

        private static void InitializeCustomHeaders(System.Net.Http.HttpClient item, NameValueHeaderValueCollection customHeaders)
        {
            if (item == null)
                return;
            if (customHeaders != null)
            {
                foreach (var customHeader in customHeaders)
                {
                    if (item.DefaultRequestHeaders.Contains(customHeader.Name))
                        item.DefaultRequestHeaders.Remove(customHeader.Name);
                    item.DefaultRequestHeaders.Add(customHeader.Name, customHeader.Value);
                }
            }
        }

        #endregion

        #endregion
        #region Constructors

        public HttpClient()
        {
        }
        public HttpClient(HttpClientSettings settings)
        {
            this.Settings = settings;
        }

        #endregion
    }
}
